#pragma once
#include <iostream>
#include <queue>

#include <unistd.h>
#include <pthread.h>
const int gmaxcap = 5;

template <class T>
class BlockQueue
{
public:
    BlockQueue(const int &maxcap = gmaxcap) : _maxcap(maxcap)
    {
        // 初始化锁
        pthread_mutex_init(&_mutex, nullptr);

        // 初始化条件变量
        pthread_cond_init(&_ccond, nullptr);
        pthread_cond_init(&_pcond, nullptr);
    }

    ~BlockQueue()
    {
        // 销毁锁和条件变量
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_ccond);
        pthread_cond_destroy(&_pcond);
    }

    // 生产者向阻塞队列插入数据
    void push(const T &in) // 输入型参数，一般使用const &
    {
        // 1.先加锁
        pthread_mutex_lock(&_mutex);

        // 2.生产
        // 细节2
        // pthread_cond_wait调用会不会失败呢？会造成什么bug?
        // 10个生产者，一个消费者。消费者只腾出一个空间，生产者同时唤醒会不会造成多次push呢？
        // 所以，我们只能使用while循环去判断，而不是使用if去判断。因为唤醒有可能是伪唤醒
        //  当线程醒来之后，需要重新再次判断是否为满。
        while (is_full()) // 无法生产，生产者等待消费者消费后生产
        {
            // 细节1，wait函数的第二个参数必须是我们正在使用的互斥锁
            // a. pthread_cond_wait:该函数会以原子性的操作。将锁释放，并将自己挂起
            // b. pthread_cond_wait被唤醒返回的的时候，会以原子性操作，自动将输入的锁加锁。
            pthread_cond_wait(&_pcond, &_mutex);
        }

        // 没满，插入数据
        _q.push(in);

        // 此时队列里有生产的数据了,唤醒消费者进行消费
        // 当然，也可以设定一定的策略来唤醒消费者。比如队列数据达到一般就唤醒
        // 细节3：signal可以放在临界区外部，也可以放在临界区内部
        pthread_cond_signal(&_ccond);

        // 3.解锁
        pthread_mutex_unlock(&_mutex);
    }

    // 消费者消费数据
    void pop(T *out) // 输出型参数，一般使用指针
    {
        // 1.加锁
        pthread_mutex_lock(&_mutex);

        // 2.消费
        // 队列为空，消费者休眠
        while (is_empty())
        {
            pthread_cond_wait(&_ccond, &_mutex);
        }

        // 消费数据
        *out = _q.front();
        _q.pop();

        // 此时队列不为满，可以唤醒生产者生产数据
        pthread_cond_signal(&_pcond);

        // 3.解锁
        pthread_mutex_unlock(&_mutex);
    }

private:
    bool is_empty()
    {
        return _q.empty();
    }

    bool is_full()
    {
        return _q.size() == _maxcap;
    }

private:
    std::queue<T> _q;       // 阻塞队列
    int _maxcap;            // 表示阻塞队列的最大容量
    pthread_mutex_t _mutex; // 阻塞队列的互斥锁，同时只有一个线程进行访问
    pthread_cond_t _pcond;  // 生产者对应的条件变量，满了要休眠
    pthread_cond_t _ccond;  // 消费者对应的条件变量，为空去休眠
};